package com.hy.video.monitor.service;

import com.hy.video.monitor.domain.User;
import com.hy.video.monitor.util.Constants;
import com.hy.video.monitor.util.CryptoUtil;
import com.hy.video.monitor.util.TotpUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.redisson.api.RMapCache;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.security.InvalidKeyException;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

@Slf4j
@RequiredArgsConstructor
@Service
@Transactional
public class UserCacheService {

    private final RedissonClient redisson; // redis客户端
    private final CryptoUtil cryptoUtil; // 加密工具
    private final TotpUtil totpUtil; // 一次性密码工具

    /**
     * 根据mfaId缓存用户信息
     * @param user
     * @return
     */
    @Transactional
    public String cacheUser(User user) {
        val mfaId = cryptoUtil.randomAlphanumeric(12);
        log.debug("生成 mfaId: {}", mfaId);
        // RMapCache: 映射缓存(MapCache)它能够保留插入元素的顺序，并且可以指明每个元素的过期时间
        RMapCache<String, User> cache = redisson.getMapCache(Constants.CACHE_MFA);// redis中分类存储用户缓存的key
        if (!cache.containsKey(mfaId)) {
            // 缓存键值的有效期为前面TOTP设置好的验证码有效期,利用redis的设置有效期机制，实现删除缓存中过期验证码数据
            cache.put(mfaId, user, totpUtil.getTimeStepInLong(), TimeUnit.SECONDS);
        }
        return mfaId;
    }

    /**
     * 根据mfaId从缓存中获取用户信息，为什么定义为Optional，因为缓存中可能没有数据
     * @param mfaId
     * @return
     */
    public Optional<User> retrieveUser(String mfaId) {
        log.debug("输入参数 mfaId: {}", mfaId);
        RMapCache<String, User> cache = redisson.getMapCache(Constants.CACHE_MFA);
        if (cache.containsKey(mfaId)) {
            log.debug("找到 mfaId {}", mfaId);
            return Optional.of(cache.get(mfaId));
        }
        return Optional.empty();
    }

    /**
     * 验证码校验
     * @param mfaId 生成用户验证码的私有key，user给每个用户有存储
     * @param code 验证码
     * @return
     */
    @Transactional
    public Optional<User> verifyTotp(String mfaId, String code) {
        log.debug("输入参数 mfaId: {}, code: {}", mfaId, code);
        RMapCache<String, User> cache = redisson.getMapCache(Constants.CACHE_MFA);
        if (!cache.containsKey(mfaId) || cache.get(mfaId) == null) {
            return Optional.empty();
        }
        val cachedUser = cache.get(mfaId);
        log.debug("找到用户 {}", cachedUser);
        try {
            val isValid = totpUtil.validateTotp(totpUtil.decodeKeyFromString(cachedUser.getMfaKey()), code);
            log.debug("code {} 的验证结果为 {}", code, isValid);
            if (!isValid) {
                return Optional.empty();
            }
            cache.remove(mfaId);// 二次验证码校验通过后删除redis缓存的key-mfaId值
            log.debug("移除 mfaId: {}", mfaId);
            return Optional.of(cachedUser);
        } catch (InvalidKeyException e) {
            log.error("Key is invalid {}", e.getLocalizedMessage());
        }
        return Optional.empty();
    }
}
